package matrice;

import controleur.QRCommon;

import dataMode.QRData;


public class MaskPattern {
	
	private int numMask;
	private int score;
	private Boolean[][] matriceMask, matriceInit; // MatriceMask: matrice qui va générer l'image | matriceInit matrice qui va permettre d'appliquer tous les masques 
	private boolean[] data; // Chaine binaire pour remplir la matrice 
	private int taille; // taille de la matrice
	private int[][] typeInformation; // équivalent pour tous les mask
	
	/** Constructeur
	 * 
	 * @param numMask
	 * @param mat
	 */
	public MaskPattern(Boolean[][] mat, QRData qrdata) {
	
		this.score = 0;
		this.taille = mat.length;
		this.matriceInit = mat;

		afficherMatMask(matriceInit);

		this.matriceMask = new Boolean[taille][taille];
		this.data = QRCommon.stringToBoolean(qrdata.getBinaryData());
		this.typeInformation = QRCommon.getTypeInformation(qrdata.getErrorCorrectionLevel());
		
		this.setBestMask();
	}
	
	/** Détermine le meilleur mask
	 * 	Déroulement :
	 * 		1- Remplit la matrice en fonction du mask	
	 * 			-> 1 - Ajoute le type Information en fonction du mask
	 * 			-> 2 - Remplit la matrice en fonction de switchModule()
	 * 		2- Détermine le score de la matrice courante
	 * 		3- garde le mask retournant le meilleur score
	 */
	public void setBestMask() { 
		
		int currentMask, bestScore, bestMask;
		bestScore = 0;
		bestMask = 0;
		EvalMask eval = new EvalMask();
		
		for(currentMask = 0; currentMask < 8; currentMask++){  
		
			// Détermine le mask courant
			this.setNumMask(currentMask); 
			
			// Récupere la matrice sans data			
			initMatrice();

			// applique le mask courant à la matrice
			this.switchMatrice(); 
			
			// Donne la nouvelle matrice pour l'évaluation
			eval.setMatrice(this.getMatrice());
	
			// détermine le score de la nouvelle matrice
			this.setScore(eval.eval()); 
			
			// Garde la matrice avec le meilleur score
			if(bestScore > this.score){
				bestScore = this.score;
				bestMask = this.numMask;

			}else if( bestScore == 0){
				bestScore = this.score;
				bestMask = this.numMask;
			}
		}
		// Construit le meilleure QRCode
		this.initMatrice();
		this.setNumMask(bestMask);
		this.switchMatrice();
	}
	
	/** applique le masque sur l'emsemble de la matrice
	 * 
	 * @return
	 */
	public void switchMatrice(){
		
		this.addTypeInformation();
		int iLigne, iColonne; // indice Ligne - colonne 
		int countModule, indParcours; 
		iColonne = this.taille-1;
		iLigne = this.taille-1;
		countModule = 0; // compteur du nombre de block de data ajouté
		indParcours = -1; // permet le parcours de la matrice en montant ou descendant
		
		while(iLigne >= 0 && iColonne >= 0){
			
			if(iColonne == 6) // On saute le timing pattern
				iColonne--;
				
			for(int col = iColonne; col >= iColonne-1 && col >= 0; col--){ // parcours 2 colonnes en commencant par la dernière
				
				// si le module de la matrice n'est pas remplit 
				if(this.matriceMask[iLigne][col] == null) { 
					
					// si l'on a besoin de changer la valeur du module  
					if(switchModule(numMask, iLigne, col)){
						this.matriceMask[iLigne][col] = !data[countModule]; // on change
					}
					else{
						this.matriceMask[iLigne][col] = data[countModule]; // on laisse
					}
					this.afficherMatMask(matriceMask);
					countModule++;
				}
			}
			
			// si on a parcouru toutes les lignes 
			if((iLigne == taille-1 && indParcours == 1 )|| 
			  (iLigne == 0 && indParcours == -1)) {	 
				
					// on change le sens de traitement
						indParcours = -indParcours;
					// On decale de 2 colonnes vers la gauche
						iColonne = iColonne-2; 
			}
			else {
				// parcours des lignes en fonctions du sens de parcours
				iLigne += indParcours; 
			}
		}
	}
	
	/** Ajoute le type information à la matrice en fonction du numéro du mask
	 *
	 */
	void addTypeInformation() {
		
		// ligne horizontal
		int i = 0;
		for(int col = 0; col < 6; col++){
				this.matriceMask[8][col] = QRCommon.intToBoolean(typeInformation[this.numMask][i]);
				i++;
		}
		this.matriceMask[8][7]=QRCommon.intToBoolean(typeInformation[this.numMask][i]);
		i++;
		for(int col=(this.taille-8); col < this.taille ;col++){
			this.matriceMask[8][col]=QRCommon.intToBoolean(typeInformation[this.numMask][i]);
			i++;
		}
		// ligne verticale	
		int j=0;
		for(int lng=(this.taille-1); lng > (this.taille - 8); lng--){
			this.matriceMask[lng][8]=QRCommon.intToBoolean(typeInformation[this.numMask][j]);
			j++;
		}
		this.matriceMask[8][8]=QRCommon.intToBoolean(typeInformation[this.numMask][j]);
		j++;
		this.matriceMask[7][8]=QRCommon.intToBoolean(typeInformation[this.numMask][j]);
		j++;
		for(int lng=5; lng>0; lng--){
			this.matriceMask[lng][8]=QRCommon.intToBoolean(typeInformation[this.numMask][j]);
			j++;
		}
		this.matriceMask[0][8]=QRCommon.intToBoolean(typeInformation[this.numMask][j]);
	}
	
	/** permet de savoir si l'on doit changer la couleur du module(pixel), selon le mask, dans la matrice
	 * 
	 * @param numMask 
	 * @param ligne
	 * @param colonne	
	 * @return 
	 */
	private Boolean switchModule(int numMask, int ligne, int colonne){
		
		switch(numMask){
		
		case 0: 
			return (ligne + colonne)%2 == 0;
			
		case 1:
			return ligne%2 == 0;
				
		case 2:
			return colonne%3 == 0;
			
		case 3:
			return (ligne+colonne)%3 == 0;
			
		case 4:
			return ((ligne/2)+(colonne/3))%2 == 0;
			
		case 5:
			return ((ligne*colonne)%2 + (ligne*colonne)%3) == 0;
			
		case 6:
			return ((ligne * colonne)%2 + (ligne*colonne)%3)%2 == 0;
			
		case 7:
			return ((ligne*colonne)%3 + (ligne+colonne)%2)%2 == 0;

		default:
			throw new IllegalArgumentException("Mask incompatible :"+ numMask);
		}	
	}
	
	public void afficherMatMask(Boolean[][] mat){
	/*	for(int i = 0; i < this.taille; i++){
			System.out.println();
			for(int j = 0; j < this.taille; j++){
				if(mat[i][j] == null)
					System.out.print(2);
				else if(mat[i][j] == true){
					System.out.print(1);
				}else if(mat[i][j] == false){
					System.out.print(0);
				}
			}
		}*/
	}
	
	/** Accès à la matrice
	 * 
	 * @return matriceMask
	 */
	public Boolean[][] getMatrice() {
		return this.matriceMask;
	}
	
	/** Détermine le score
	 * 
	 * @param unScore
	 */
	public void setScore(int unScore){
		this.score = unScore;
	}
	/** Accès au score 
	 * 
	 * @return score
	 */
	public int getScore(){
		return this.score;
	}
	
	/** Détermine le numéro du mask
	 * 
	 * @param num
	 */
	public void setNumMask(int num){
		this.numMask = num;
	}
	/** Accès au numéro du mask
	 * 
	 * @return
	 */
	public int getNumMask() {
		return this.numMask;
	}
	
	/** Change seulement le numéro et la matrice du mask ( les autres données ne changent pas )
	 * 
	 * @param ms
	 */
	public void setMask(MaskPattern ms){
		this.numMask = ms.numMask;
		this.matriceMask = ms.getMatrice();
	}
	
	public void initMatrice(){
		for(int i=0; i < taille; i++) {
			for(int j = 0 ; j < taille; j++){
			matriceMask[i][j] = matriceInit[i][j];
			}
		}
	}
	
	public int nbBlockNull(){
		int nb = 0;
		
		for(int i = 0; i < taille; i++){
			for(int j = 0; j < taille; j++){
				if(this.matriceInit[i][j] == null){
					nb++;
				}
			}
		}
		
		return nb;
	}
}